{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using backend: pytorch\n"
     ]
    }
   ],
   "source": [
    "import dgl.nn as dglnn\n",
    "from dgl import from_networkx\n",
    "import torch.nn as nn\n",
    "import torch as th\n",
    "import torch.nn.functional as F\n",
    "import dgl.function as fn\n",
    "from dgl.data.utils import load_graphs\n",
    "import networkx as nx\n",
    "import pandas as pd\n",
    "import socket\n",
    "import struct\n",
    "import random\n",
    "from sklearn.preprocessing import LabelEncoder\n",
    "from sklearn.preprocessing import StandardScaler\n",
    "from sklearn.model_selection import train_test_split\n",
    "import seaborn as sns\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/lib/python3/dist-packages/IPython/core/interactiveshell.py:3062: DtypeWarning: Columns (7,9) have mixed types. Specify dtype option on import or set low_memory=False.\n",
      "  has_raised = await self.run_ast_nodes(code_ast.body, cell_name,\n"
     ]
    }
   ],
   "source": [
    "data = pd.read_csv('./bot.csv')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.drop(columns=['subcategory','pkSeqID','stime','flgs','category','state','proto','seq'],inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.rename(columns={\"attack\": \"label\"},inplace = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1    3668045\n",
       "0        477\n",
       "Name: label, dtype: int64"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data.label.value_counts()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#le = LabelEncoder()\n",
    "#le.fit_transform(data.label.values)\n",
    "#data['label'] = le.transform(data['label'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['saddr'] = data.saddr.apply(str)\n",
    "data['sport'] = data.sport.apply(str)\n",
    "data['daddr'] = data.daddr.apply(str)\n",
    "data['dport'] = data.dport.apply(str)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['saddr'] = data.saddr.apply(lambda x: socket.inet_ntoa(struct.pack('>I', random.randint(0xac100001, 0xac1f0001))))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "data['saddr'] = data['saddr'] + ':' + data['sport']\n",
    "data['daddr'] = data['daddr'] + ':' + data['dport']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.drop(columns=['sport','dport'],inplace=True)\n",
    "label_ground_truth = data[[\"saddr\", \"daddr\",\"label\"]]\n",
    "data = pd.get_dummies(data, columns = ['flgs_number','state_number', 'proto_number'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = data.reset_index()\n",
    "data.replace([np.inf, -np.inf], np.nan,inplace = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.fillna(0,inplace = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "label_ground_truth = data[[\"saddr\", \"daddr\",\"label\"]]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "data.drop(columns=['index'],inplace=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>saddr</th>\n",
       "      <th>daddr</th>\n",
       "      <th>pkts</th>\n",
       "      <th>bytes</th>\n",
       "      <th>ltime</th>\n",
       "      <th>dur</th>\n",
       "      <th>mean</th>\n",
       "      <th>stddev</th>\n",
       "      <th>sum</th>\n",
       "      <th>min</th>\n",
       "      <th>...</th>\n",
       "      <th>state_number_7</th>\n",
       "      <th>state_number_8</th>\n",
       "      <th>state_number_9</th>\n",
       "      <th>state_number_10</th>\n",
       "      <th>state_number_11</th>\n",
       "      <th>proto_number_1</th>\n",
       "      <th>proto_number_2</th>\n",
       "      <th>proto_number_3</th>\n",
       "      <th>proto_number_4</th>\n",
       "      <th>proto_number_5</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>172.22.188.85:49960</td>\n",
       "      <td>192.168.100.7:80</td>\n",
       "      <td>8</td>\n",
       "      <td>1980</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>7.056393</td>\n",
       "      <td>0.068909</td>\n",
       "      <td>0.068909</td>\n",
       "      <td>0.137818</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>172.21.178.69:-1</td>\n",
       "      <td>192.168.100.147:-1</td>\n",
       "      <td>2</td>\n",
       "      <td>120</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>0.000131</td>\n",
       "      <td>0.000131</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000131</td>\n",
       "      <td>0.000131</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>172.24.211.173:49962</td>\n",
       "      <td>192.168.100.7:80</td>\n",
       "      <td>8</td>\n",
       "      <td>2126</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>7.047852</td>\n",
       "      <td>0.064494</td>\n",
       "      <td>0.064494</td>\n",
       "      <td>0.128988</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>172.18.145.65:49964</td>\n",
       "      <td>192.168.100.7:80</td>\n",
       "      <td>8</td>\n",
       "      <td>2024</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>7.047592</td>\n",
       "      <td>0.064189</td>\n",
       "      <td>0.064189</td>\n",
       "      <td>0.128378</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>172.30.55.23:49966</td>\n",
       "      <td>192.168.100.7:80</td>\n",
       "      <td>8</td>\n",
       "      <td>2319</td>\n",
       "      <td>1.528089e+09</td>\n",
       "      <td>7.046841</td>\n",
       "      <td>0.063887</td>\n",
       "      <td>0.063887</td>\n",
       "      <td>0.127774</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>...</th>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "      <td>...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668517</th>\n",
       "      <td>172.29.73.1:35064</td>\n",
       "      <td>192.168.100.3:22</td>\n",
       "      <td>6</td>\n",
       "      <td>434</td>\n",
       "      <td>1.529381e+09</td>\n",
       "      <td>0.013165</td>\n",
       "      <td>0.013165</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.013165</td>\n",
       "      <td>0.013165</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668518</th>\n",
       "      <td>172.24.28.79:35066</td>\n",
       "      <td>192.168.100.3:22</td>\n",
       "      <td>6</td>\n",
       "      <td>434</td>\n",
       "      <td>1.529381e+09</td>\n",
       "      <td>0.000574</td>\n",
       "      <td>0.000574</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000574</td>\n",
       "      <td>0.000574</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668519</th>\n",
       "      <td>172.26.187.152:35070</td>\n",
       "      <td>192.168.100.3:22</td>\n",
       "      <td>31</td>\n",
       "      <td>5472</td>\n",
       "      <td>1.529381e+09</td>\n",
       "      <td>2.874302</td>\n",
       "      <td>2.874302</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>2.874302</td>\n",
       "      <td>2.874302</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668520</th>\n",
       "      <td>172.27.238.214:43001</td>\n",
       "      <td>192.168.100.150:4433</td>\n",
       "      <td>2</td>\n",
       "      <td>134</td>\n",
       "      <td>1.529381e+09</td>\n",
       "      <td>0.000003</td>\n",
       "      <td>0.000003</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000003</td>\n",
       "      <td>0.000003</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3668521</th>\n",
       "      <td>172.17.124.242:-1</td>\n",
       "      <td>192.168.100.149:-1</td>\n",
       "      <td>16</td>\n",
       "      <td>960</td>\n",
       "      <td>1.529382e+09</td>\n",
       "      <td>848.002686</td>\n",
       "      <td>0.000145</td>\n",
       "      <td>0.000056</td>\n",
       "      <td>0.001160</td>\n",
       "      <td>0.000080</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>3668522 rows × 58 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                        saddr                 daddr  pkts  bytes  \\\n",
       "0         172.22.188.85:49960      192.168.100.7:80     8   1980   \n",
       "1            172.21.178.69:-1    192.168.100.147:-1     2    120   \n",
       "2        172.24.211.173:49962      192.168.100.7:80     8   2126   \n",
       "3         172.18.145.65:49964      192.168.100.7:80     8   2024   \n",
       "4          172.30.55.23:49966      192.168.100.7:80     8   2319   \n",
       "...                       ...                   ...   ...    ...   \n",
       "3668517     172.29.73.1:35064      192.168.100.3:22     6    434   \n",
       "3668518    172.24.28.79:35066      192.168.100.3:22     6    434   \n",
       "3668519  172.26.187.152:35070      192.168.100.3:22    31   5472   \n",
       "3668520  172.27.238.214:43001  192.168.100.150:4433     2    134   \n",
       "3668521     172.17.124.242:-1    192.168.100.149:-1    16    960   \n",
       "\n",
       "                ltime         dur      mean    stddev       sum       min  \\\n",
       "0        1.528089e+09    7.056393  0.068909  0.068909  0.137818  0.000000   \n",
       "1        1.528089e+09    0.000131  0.000131  0.000000  0.000131  0.000131   \n",
       "2        1.528089e+09    7.047852  0.064494  0.064494  0.128988  0.000000   \n",
       "3        1.528089e+09    7.047592  0.064189  0.064189  0.128378  0.000000   \n",
       "4        1.528089e+09    7.046841  0.063887  0.063887  0.127774  0.000000   \n",
       "...               ...         ...       ...       ...       ...       ...   \n",
       "3668517  1.529381e+09    0.013165  0.013165  0.000000  0.013165  0.013165   \n",
       "3668518  1.529381e+09    0.000574  0.000574  0.000000  0.000574  0.000574   \n",
       "3668519  1.529381e+09    2.874302  2.874302  0.000000  2.874302  2.874302   \n",
       "3668520  1.529381e+09    0.000003  0.000003  0.000000  0.000003  0.000003   \n",
       "3668521  1.529382e+09  848.002686  0.000145  0.000056  0.001160  0.000080   \n",
       "\n",
       "         ...  state_number_7  state_number_8  state_number_9  state_number_10  \\\n",
       "0        ...               0               0               0                0   \n",
       "1        ...               0               0               0                0   \n",
       "2        ...               0               0               0                0   \n",
       "3        ...               0               0               0                0   \n",
       "4        ...               0               0               0                0   \n",
       "...      ...             ...             ...             ...              ...   \n",
       "3668517  ...               0               0               0                0   \n",
       "3668518  ...               0               0               0                0   \n",
       "3668519  ...               0               0               0                0   \n",
       "3668520  ...               0               0               0                0   \n",
       "3668521  ...               0               0               0                0   \n",
       "\n",
       "         state_number_11  proto_number_1  proto_number_2  proto_number_3  \\\n",
       "0                      0               1               0               0   \n",
       "1                      0               0               1               0   \n",
       "2                      0               1               0               0   \n",
       "3                      0               1               0               0   \n",
       "4                      0               1               0               0   \n",
       "...                  ...             ...             ...             ...   \n",
       "3668517                0               1               0               0   \n",
       "3668518                0               1               0               0   \n",
       "3668519                0               1               0               0   \n",
       "3668520                0               1               0               0   \n",
       "3668521                0               0               1               0   \n",
       "\n",
       "         proto_number_4  proto_number_5  \n",
       "0                     0               0  \n",
       "1                     0               0  \n",
       "2                     0               0  \n",
       "3                     0               0  \n",
       "4                     0               0  \n",
       "...                 ...             ...  \n",
       "3668517               0               0  \n",
       "3668518               0               0  \n",
       "3668519               0               0  \n",
       "3668520               0               0  \n",
       "3668521               0               0  \n",
       "\n",
       "[3668522 rows x 58 columns]"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "scaler = StandardScaler()\n",
    "cols_to_norm = list(set(list(data.iloc[:, 2:].columns ))  - set(list(['label'])) )\n",
    "data[cols_to_norm] = scaler.fit_transform(data[cols_to_norm])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "X_train, X_test, y_train, y_test = train_test_split(\n",
    "     data, label_ground_truth, test_size=0.3, random_state=42, stratify=label_ground_truth.label)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-18-841b8327893c>:1: SettingWithCopyWarning: \n",
      "A value is trying to be set on a copy of a slice from a DataFrame.\n",
      "Try using .loc[row_indexer,col_indexer] = value instead\n",
      "\n",
      "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
      "  X_train['h'] = X_train[ cols_to_norm ].values.tolist()\n"
     ]
    }
   ],
   "source": [
    "X_train['h'] = X_train[ cols_to_norm ].values.tolist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "#from dgl.data.utils import load_graphs   \n",
    "#G = load_graphs(\"./data.bin\")[0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = nx.from_pandas_edgelist(X_train, \"saddr\", \"daddr\", ['h','label'], create_using= nx.MultiGraph())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = G.to_directed()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = from_networkx(G,edge_attrs=['h','label'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [],
   "source": [
    "#from dgl.data.utils import save_graphs\n",
    "#save_graphs(\"./data.bin\", [G])\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Eq1\n",
    "G.ndata['h'] = th.ones(G.num_nodes(), G.edata['h'].shape[1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "G.edata['train_mask'] = th.ones(len(G.edata['h']), dtype= th.bool)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "#G = load_graphs(\"./bot_train_G.bin\") [0][0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "G.ndata['h'] = th.reshape(G.ndata['h'], (G.ndata['h'].shape[0], 1, G.ndata['h'].shape[1]))\n",
    "G.edata['h'] = th.reshape(G.edata['h'], (G.edata['h'].shape[0], 1, G.edata['h'].shape[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MLPPredictor(nn.Module):\n",
    "    def __init__(self, in_features, out_classes):\n",
    "        super().__init__()\n",
    "        self.W = nn.Linear(in_features * 2, out_classes)\n",
    "\n",
    "    def apply_edges(self, edges):\n",
    "        h_u = edges.src['h']\n",
    "        h_v = edges.dst['h']\n",
    "        score = self.W(th.cat([h_u, h_v], 1))\n",
    "        return {'score': score}\n",
    "\n",
    "    def forward(self, graph, h):\n",
    "        with graph.local_scope():\n",
    "            graph.ndata['h'] = h\n",
    "            graph.apply_edges(self.apply_edges)\n",
    "            return graph.edata['score']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([2581116, 1, 55])"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G.ndata['h'].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [],
   "source": [
    "def compute_accuracy(pred, labels):\n",
    "    return (pred.argmax(1) == labels).float().mean().item()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "class SAGELayer(nn.Module):\n",
    "    def __init__(self, ndim_in, edims, ndim_out, activation):\n",
    "        super(SAGELayer, self).__init__()\n",
    "        ### force to outut fix dimensions\n",
    "        self.W_msg = nn.Linear(ndim_in + edims, ndim_out)\n",
    "        ### apply weight\n",
    "        self.W_apply = nn.Linear(ndim_in + ndim_out, ndim_out)\n",
    "        self.activation = activation\n",
    "\n",
    "    def message_func(self, edges):\n",
    "        return {'m': self.W_msg(th.cat([edges.src['h'], edges.data['h']], 2))}\n",
    "\n",
    "    def forward(self, g_dgl, nfeats, efeats):\n",
    "        with g_dgl.local_scope():\n",
    "            g = g_dgl\n",
    "            g.ndata['h'] = nfeats\n",
    "            g.edata['h'] = efeats\n",
    "            # Eq4\n",
    "            g.update_all(self.message_func, fn.mean('m', 'h_neigh'))\n",
    "            # Eq5          \n",
    "            g.ndata['h'] = F.relu(self.W_apply(th.cat([g.ndata['h'], g.ndata['h_neigh']], 2)))\n",
    "            return g.ndata['h']\n",
    "\n",
    "\n",
    "class SAGE(nn.Module):\n",
    "    def __init__(self, ndim_in, ndim_out, edim, activation, dropout):\n",
    "        super(SAGE, self).__init__()\n",
    "        self.layers = nn.ModuleList()\n",
    "        self.layers.append(SAGELayer(ndim_in, edim, 128, activation))\n",
    "        self.layers.append(SAGELayer(128, edim, ndim_out, activation))\n",
    "        self.dropout = nn.Dropout(p=dropout)\n",
    "\n",
    "    def forward(self, g, nfeats, efeats):\n",
    "        for i, layer in enumerate(self.layers):\n",
    "            if i != 0:\n",
    "                nfeats = self.dropout(nfeats)\n",
    "            nfeats = layer(g, nfeats, efeats)\n",
    "        return nfeats.sum(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Model(nn.Module):\n",
    "    def __init__(self, ndim_in, ndim_out, edim, activation, dropout):\n",
    "        super().__init__()\n",
    "        self.gnn = SAGE(ndim_in, ndim_out, edim, activation, dropout)\n",
    "        self.pred = MLPPredictor(ndim_out, 2)\n",
    "    def forward(self, g, nfeats, efeats):\n",
    "        h = self.gnn(g, nfeats, efeats)\n",
    "        return self.pred(g, h)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.utils import class_weight\n",
    "class_weights = class_weight.compute_class_weight('balanced',\n",
    "                                                 np.unique(G.edata['label'].cpu().numpy()),\n",
    "                                                 G.edata['label'].cpu().numpy())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [],
   "source": [
    "class_weights = th.FloatTensor(class_weights).cuda()\n",
    "criterion = nn.CrossEntropyLoss(weight = class_weights)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "G = G.to('cuda:0')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "device(type='cuda', index=0)"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G.device"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "device(type='cuda', index=0)"
      ]
     },
     "execution_count": 39,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G.ndata['h'].device  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "device(type='cuda', index=0)"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "G.edata['h'].device  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch: 100  Training acc: 0.9999405741691589\n",
      "Epoch: 200  Training acc: 0.9999445080757141\n",
      "Epoch: 300  Training acc: 0.999947190284729\n",
      "Epoch: 400  Training acc: 0.9999446868896484\n",
      "Epoch: 500  Training acc: 0.9999499320983887\n",
      "Epoch: 600  Training acc: 0.9999452829360962\n"
     ]
    }
   ],
   "source": [
    "node_features = G.ndata['h']\n",
    "edge_features = G.edata['h']\n",
    "\n",
    "edge_label = G.edata['label']\n",
    "train_mask = G.edata['train_mask']\n",
    "\n",
    "model = Model(G.ndata['h'].shape[2], 128, G.ndata['h'].shape[2], F.relu, 0.2).cuda()\n",
    "\n",
    "opt = th.optim.Adam(model.parameters())\n",
    "\n",
    "for epoch in range(1,700):\n",
    "    pred = model(G, node_features,edge_features).cuda()\n",
    "    loss = criterion(pred[train_mask] ,edge_label[train_mask])\n",
    "    opt.zero_grad()\n",
    "    loss.backward()\n",
    "    opt.step()\n",
    "    if epoch % 100 == 0:\n",
    "        print('Epoch:', epoch ,' Training acc:', compute_accuracy(pred[train_mask], edge_label[train_mask]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Model(\n",
       "  (gnn): SAGE(\n",
       "    (layers): ModuleList(\n",
       "      (0): SAGELayer(\n",
       "        (W_apply): Linear(in_features=110, out_features=128, bias=True)\n",
       "      )\n",
       "      (1): SAGELayer(\n",
       "        (W_apply): Linear(in_features=183, out_features=128, bias=True)\n",
       "      )\n",
       "    )\n",
       "    (dropout): Dropout(p=0.2, inplace=False)\n",
       "  )\n",
       "  (pred): MLPPredictor(\n",
       "    (W): Linear(in_features=256, out_features=2, bias=True)\n",
       "  )\n",
       ")"
      ]
     },
     "execution_count": 67,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model.load_state_dict(th.load('model.pt'))\n",
    "model.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "<ipython-input-42-84be06ca8888>:1: SettingWithCopyWarning: \n",
      "A value is trying to be set on a copy of a slice from a DataFrame.\n",
      "Try using .loc[row_indexer,col_indexer] = value instead\n",
      "\n",
      "See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
      "  X_test['h'] = X_test[ cols_to_norm ].values.tolist()\n"
     ]
    }
   ],
   "source": [
    "X_test['h'] = X_test[ cols_to_norm ].values.tolist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "#G_test = load_graphs(\"bot_test_G.bin\") [0][0]\n",
    "G_test = nx.from_pandas_edgelist(X_test, \"saddr\", \"daddr\", ['h','label'],create_using=nx.MultiGraph())\n",
    "G_test = G_test.to_directed()\n",
    "G_test = from_networkx(G_test,edge_attrs=['h','label'] )\n",
    "actual = G_test.edata.pop('label')\n",
    "G_test.ndata['feature'] = th.ones(G_test.num_nodes(), 55)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [],
   "source": [
    "G_test.ndata['feature'] = th.reshape(G_test.ndata['feature'], (G_test.ndata['feature'].shape[0], 1, G_test.ndata['feature'].shape[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [],
   "source": [
    "G_test.edata['h'] = th.reshape(G_test.edata['h'], (G_test.edata['h'].shape[0], 1, G_test.edata['h'].shape[1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [],
   "source": [
    "G_test = G_test.to('cuda:0')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [],
   "source": [
    "th.cuda.empty_cache()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "metadata": {},
   "outputs": [],
   "source": [
    "import timeit\n",
    "start_time = timeit.default_timer()\n",
    "node_features_test = G_test.ndata['feature']\n",
    "edge_features_test = G_test.edata['h']\n",
    "test_pred = model(G_test, node_features_test, edge_features_test).cuda()\n",
    "elapsed = timeit.default_timer() - start_time"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.26155815399943094 seconds\n"
     ]
    }
   ],
   "source": [
    "print(str(elapsed) + ' seconds')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_pred = test_pred.argmax(1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_pred = th.Tensor.cpu(test_pred).detach().numpy()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [],
   "source": [
    "actual = [\"Normal\" if i == 0 else \"Attack\" for i in actual]\n",
    "test_pred = [\"Normal\" if i == 0 else \"Attack\" for i in test_pred]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_confusion_matrix(cm,\n",
    "                          target_names,\n",
    "                          title='Confusion matrix',\n",
    "                          cmap=None,\n",
    "                          normalize=True):\n",
    "    \n",
    "    import matplotlib.pyplot as plt\n",
    "    import numpy as np\n",
    "    import itertools\n",
    "\n",
    "    accuracy = np.trace(cm) / float(np.sum(cm))\n",
    "    misclass = 1 - accuracy\n",
    "\n",
    "    if cmap is None:\n",
    "        cmap = plt.get_cmap('Blues')\n",
    "\n",
    "    plt.figure(figsize=(12, 12))\n",
    "    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n",
    "    plt.title(title)\n",
    "    plt.colorbar()\n",
    "\n",
    "    if target_names is not None:\n",
    "        tick_marks = np.arange(len(target_names))\n",
    "        plt.xticks(tick_marks, target_names, rotation=45)\n",
    "        plt.yticks(tick_marks, target_names)\n",
    "\n",
    "    if normalize:\n",
    "        cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n",
    "\n",
    "\n",
    "    thresh = cm.max() / 1.5 if normalize else cm.max() / 2\n",
    "    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n",
    "        if normalize:\n",
    "            plt.text(j, i, \"{:0.4f}\".format(cm[i, j]),\n",
    "                     horizontalalignment=\"center\",\n",
    "                     color=\"white\" if cm[i, j] > thresh else \"black\")\n",
    "        else:\n",
    "            plt.text(j, i, \"{:,}\".format(cm[i, j]),\n",
    "                     horizontalalignment=\"center\",\n",
    "                     color=\"white\" if cm[i, j] > thresh else \"black\")\n",
    "\n",
    "\n",
    "    plt.tight_layout()\n",
    "    plt.ylabel('True label')\n",
    "    plt.xlabel('Predicted label\\naccuracy={:0.4f}; misclass={:0.4f}'.format(accuracy, misclass))\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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oQYAABg17WcAADBjrOg8HZUaAABg1IQaAABg1LSfAQDADKkkS6x/NhWVGlgkXzji8Nxulx2zy063yr+89jWLPRyABXPmmWdm3/veK7e/3c65w2675E1v/PckyXdPPDH3uOudsvceu+eue++ZY7/97Stfc9RXv5K999g9d9htl9zv3vdcrKEDI6VSA4vg8ssvz9886+n53OePzDbbbpu73emOeeADH5zb7LzzYg8N4BrbcMMN85rXvj63v8Md8pvf/CZ32XuP3Oe+98uLX/SCvPgfXpp999s/h3/+sLz4RS/IF774lVx00UV59jOflk9/9vBst912Offccxf7LQAjI9TAIjj229/OLW95q2x/i1skSf78kQfms5/5tFADrBNufOMb58Y3vnGSZLPNNstOO90m55xzdqoqv/71r5Mkv/rVr3Ljm9wkSfKRD30wBzzkz7LddtslSbbaaqvFGTjMEKufTUeogUVwzjlnZ9ttb3rl9jbbbJtvf/uYRRwRwJrx05/8JCee+J3cca+98y+v/7c86E/3zYv+7nm54oor8uWjvpEkOe20H2XpZZfl/vfZJxf/5jd5+jOfnUc95rGLPHJgTNbYnJqq6qp6/Zzt51XVP66p+61kDF+pqj3X5j0BgImLL744Bz3iYfmX1/9brne96+Xgt781r33dG3L6GWfmta97Q/76yU9MkixdujQnnHB8Pnno53LoYUfk1a96RU770Y8WefTAmKzJhQJ+n+TPqmqLq/PiqlJFYp11k5tsk7POOvPK7bPPPivbbLPNIo4IYGFddtllOegRD8sjD3pUHvLQP0uSfOB977ny+cMe/uc57tjJQgHbbLtt7nf/fXOd61wnW2yxRe52t3vkpJO+u2hjB8ZnTYaapUkOTvKc5Q9U1c2r6ktVdVJVfbGqthv2v7uq3lZVxyR57bD91qr6VlX9uKr2qapDqurUqnr3nOu9taqOq6qTq+pla/A9wYLY8453zOmnn5afnHFGLr300nzsIx/Onz7wwYs9LIAF0d156l89MTvudJs8+zl/e+X+G9/kJvnaUV9Nknzly1/KrW61Q5LkQQ86IN84+utZunRpLrnkkhx77DHZaafbLMrYYTbUzP9v1qzpasibk5xUVa9dbv9/JHlPd7+nqp6Q5I1JHjIc2zbJXbr78iG43CDJnZM8OMmhSe6a5ElJjq2q3bv7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",
      "text/plain": [
       "<Figure size 864x864 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from sklearn.metrics import confusion_matrix\n",
    "\n",
    "plot_confusion_matrix(cm = confusion_matrix(actual, test_pred), \n",
    "                      normalize    = False,\n",
    "                      target_names = np.unique(actual),\n",
    "                      title        = \"Confusion Matrix\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
